import type { Ignore } from 'ignore'
import type { Stats } from 'fs'
import { existsSync } from 'fs'
import { readdir, stat } from 'fs/promises'
import { relative } from './ignore'
import { join } from 'path'
import { getFileHash } from './hash'
import config from '@/config'

export const readFileStructure = async (path: string, options?: ReadFileStructureOptions) => {
  if (!options) options = {}
  const {
    RI = 0,
    lastRI = Date.now(),
    ignore,
    root = path,
    progress = () => {},
    validator = () => true
  } = options

  if (!existsSync(path)) {
    throw new Error('path not exists: ' + path)
  }

  const result: FileStructure[] = []
  const files = await readdir(path)

  for (const file of files) {
    const current = join(path, file)
    const stats = await stat(current)
    const relativePath = relative(current, root)

    if (ignore?.ignores(relativePath)) continue

    const res = validator({ relative: relativePath, absolute: current, stats })

    if (!res) continue
    if (typeof res === 'string') throw new Error(res)
    if (res instanceof Error) throw res

    if (stats.isDirectory()) {
      result.push({
        name: file,
        type: 'dir',
        children: await readFileStructure(current, { ...options, root })
      })
    } else {
      progress(relativePath, stats.size, 0)

      let hash: string

      if (stats.size === 0) {
        hash = 'empty'
      } else {
        hash = await getFileHash(current, {
          algorithm: config.cache.hashMapAlgorithm,
          progress: (current) => progress(relativePath, stats.size, current)
        })
      }

      result.push({
        name: file,
        type: 'file',
        hash
      })

      if (RI <= 0) continue
      const now = Date.now()
      if (now - lastRI > RI) {
        options.lastRI = now
        await new Promise((resolve) => setTimeout(resolve))
      }
    }
  }

  return result
}

export type ReadFileStructureOptions = {
  RI?: number
  lastRI?: number
  ignore?: Ignore
  root?: string
  progress?: (relative: string, total: number, current: number) => void
  validator?: (data: {
    relative: string
    absolute: string
    stats: Stats
  }) => boolean | string | Error
}

export const fileStructureToHashMap = (structures: FileStructure[]) => {
  const map: HashMap = {}
  for (const structure of structures) {
    if (structure.type === 'dir') {
      map[structure.name] = fileStructureToHashMap(structure.children)
    } else {
      map[structure.name] = structure.hash
    }
  }
  return map
}

export const hashMapToFileStructure = (map: HashMap) => {
  const structures: FileStructure[] = []
  for (const key in map) {
    const value = map[key]
    if (typeof value === 'string') {
      structures.push({
        name: key,
        type: 'file',
        hash: value
      })
    } else {
      structures.push({
        name: key,
        type: 'dir',
        children: hashMapToFileStructure(value)
      })
    }
  }
  return structures
}

export const fileStructureToFlattenHashMap = (structures: FileStructure[], root = '') => {
  const map: FlattenHashMap = {}
  for (const structure of structures) {
    const key = join(root, structure.name).replace(/\\/g, '/')
    if (structure.type === 'dir') {
      map[key] = 'dir'
      const children = fileStructureToFlattenHashMap(structure.children, key)
      for (const childKey in children) {
        map[childKey] = children[childKey]
      }
    } else {
      map[key] = structure.hash
    }
  }
  return map
}

export const fileStructureDiff = (oldStructure: FileStructure[], newStructure: FileStructure[]) => {
  const oldMap = fileStructureToFlattenHashMap(oldStructure)
  const newMap = fileStructureToFlattenHashMap(newStructure)

  const result: FileStructureDiffResult = {
    removed: {},
    changed: {},
    added: {},
    sended: {},
    sended_duplicate: {},
    sended_empty: {},
    removed_count: 0,
    changed_count: 0,
    added_count: 0,
    sended_count: 0,
    sended_duplicate_count: 0,
    sended_empty_count: 0,
    hasChanged: false,
    hasSended: false
  }

  for (const key in oldMap) {
    if (newMap[key] === void 0) {
      result.removed[key] = oldMap[key]
      result.removed_count++
      result.hasChanged = true
    }
  }

  for (const key in newMap) {
    if (oldMap[key] === void 0) {
      result.added[key] = newMap[key]
      result.added_count++
      result.hasChanged = true
    } else if (oldMap[key] !== newMap[key]) {
      result.changed[key] = newMap[key]
      result.changed_count++
      result.hasChanged = true
    }
  }

  const temp: string[] = []
  const filterSended = (obj: FlattenHashMap) => {
    for (const key in obj) {
      if (obj[key] === 'dir') continue

      if (obj[key] === 'empty') {
        result.sended_empty[key] = obj[key]
        result.sended_empty_count++
        continue
      }

      result.hasSended = true

      if (!temp.includes(obj[key])) {
        temp.push(obj[key])
        result.sended[key] = obj[key]
        result.sended_count++
      } else {
        result.sended_duplicate[key] = obj[key]
        result.sended_duplicate_count++
      }
    }
  }
  filterSended(result.changed)
  filterSended(result.added)

  return result
}

export const hashMapToFileStructureOfDir = (map: HashMap) => {
  return hashMapToFileStructure(map).filter((i) => i.type === 'dir')[0]
}

export const hashMapToFileStructureOfFile = (map: HashMap) => {
  return hashMapToFileStructure(map).filter((i) => i.type === 'file')[0]
}

export const fileStructureAppend = async (
  structures: FileStructure[],
  appends: Required<AppendFileType>[]
) => {
  for (const append of appends) {
    const stats = await stat(append.from)
    if (stats.isDirectory()) {
      console.log(1)
    } else {
      console.log(2)
    }
  }
  return structures
}

const splitPath = (path: string) => path.split('/').filter((i) => i)

export type FileStructureDiffResult = {
  removed: FlattenHashMap
  changed: FlattenHashMap
  added: FlattenHashMap
  sended: FlattenHashMap
  sended_duplicate: FlattenHashMap
  sended_empty: FlattenHashMap
  removed_count: number
  changed_count: number
  added_count: number
  sended_count: number
  sended_duplicate_count: number
  sended_empty_count: number
  hasChanged: boolean
  hasSended: boolean
}

export type FileStructure = FileStructureOfDir | FileStructureOfFile

export type FileStructureOfDir = {
  /**
   * 目录名
   */
  name: string
  /**
   * 类型
   */
  type: 'dir'
  /**
   * 目录内容
   */
  children: FileStructure[]
}

export type FileStructureOfFile = {
  /**
   * 文件名
   */
  name: string
  /**
   * 类型
   */
  type: 'file'
  /**
   *  文件hash
   */
  hash: string
  /**
   * 真实路径
   */
  realPath?: string
}

export type HashMap = {
  [name: string]: HashMap | string
}

export type FlattenHashMap = {
  [path: string]: string
}

type AppendFileType = {
  from: string
  to?: string
  overwrite?: boolean
  ignore?: string[]
}
